/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.health.ecology.fa.central.processor;

import com.huawei.health.ecology.fa.central.request.AudioPlayRequest;
import com.huawei.health.ecology.fa.central.response.CentralResponseCode;
import com.huawei.health.ecology.fa.utils.LogUtil;
import com.huawei.healthecology.data.utils.ConditionOperation;
import com.huawei.healthecology.data.utils.OptionalX;
import com.huawei.healthecology.processor.HealthEcologyProcessor;

import lombok.Builder;
import lombok.NonNull;
import ohos.app.Context;
import ohos.global.resource.RawFileDescriptor;
import ohos.media.common.Source;
import ohos.media.player.Player;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * The processor for audio player
 */
public class AudioPlayerProcessor implements HealthEcologyProcessor {
    private static final String TAG = "AudioProcessor";

    private static final String AUDIO_FILE_SUFFIX = ".m4a";

    private static final String RESOURCES_DIRECTORY = "resources";

    private static final String RAW_FILE_DIRECTORY = "rawfile";

    private ConcurrentLinkedQueue<String> audioQueue;

    private Context abilityContext;

    private Player audioPlayer;

    /**
     * constructor
     *
     * @param context Context
     */
    @Builder(builderMethodName = "hiddenBuilder")
    private AudioPlayerProcessor(@NonNull Context context) {
        this.abilityContext = context;
    }

    /**
     * AudioProcessor Builder
     *
     * @param context Context
     * @return AudioProcessorBuilder audio processor builder
     */
    public static AudioPlayerProcessorBuilder builder(Context context) {
        return hiddenBuilder().context(context);
    }

    @Override
    public void initProcessor() {
        this.audioQueue = new ConcurrentLinkedQueue<>();
    }

    @Override
    public void releaseResource() {
    }

    @Override
    public void destroyProcessor() {
        Optional.ofNullable(audioPlayer).ifPresent(player -> {
            player.stop();
            player.reset();
            player.release();
        });
        audioQueue.clear();
        audioQueue = null;
        audioPlayer = null;
        abilityContext = null;
    }

    /**
     * To play the list of audio
     *
     * @param audioPlayRequest The audio play request
     * @return the central response code
     */
    public CentralResponseCode playAudioList(AudioPlayRequest audioPlayRequest) {
        Optional.of(audioPlayRequest.getAudioFileList()).ifPresent(audioQueue::addAll);
        playMediaInQueue();
        return CentralResponseCode.OPERATION_SUCCESS;
    }

    /**
     * To stop the audio playing task
     *
     * @return the central response code
     */
    public CentralResponseCode stopAudioPlay() {
        Optional.ofNullable(audioPlayer)
            .map(player -> {
                audioQueue.clear();
                return player.stop();
            });
        return CentralResponseCode.OPERATION_SUCCESS;
    }

    private synchronized void playMediaInQueue() {
        OptionalX.ofNullable(audioPlayer)
            .ifPresent(player ->
                ConditionOperation.of(audioPlayer.isNowPlaying()).ifNotExist(available -> playAudio()))
            .ifNotPresent(this::playAudio);
    }

    private void playAudio() {
        String audioPath = audioQueue.poll();
        Path path = Paths.get(RESOURCES_DIRECTORY, RAW_FILE_DIRECTORY, audioPath + AUDIO_FILE_SUFFIX);
        try (RawFileDescriptor fileDescriptor =
                abilityContext.getResourceManager().getRawFileEntry(path.toString()).openRawFileDescriptor()) {
            Source source = new Source(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartPosition(),
                fileDescriptor.getFileSize());
            audioPlayer = new Player(abilityContext);
            audioPlayer.setPlayerCallback(getCallback());
            audioPlayer.setSource(source);
            audioPlayer.prepare();
            audioPlayer.play();
        } catch (IOException | IllegalArgumentException | SecurityException | IllegalStateException exception) {
            LogUtil.error(TAG, "playAudio exception: " + exception.getMessage());
        }
    }

    private Player.IPlayerCallback getCallback() {
        return new Player.IPlayerCallback() {
            @Override
            public void onPrepared() {
            }

            @Override
            public void onMessage(int type, int extra) {
            }

            @Override
            public void onError(int errorType, int errorCode) {
                LogUtil.error(TAG, String.format(Locale.ROOT,
                    "Audio player error, errorType = %1$d and errorCode =  %2$d.", errorType, errorCode));
                Optional.ofNullable(audioQueue)
                    .filter(queue -> !queue.isEmpty())
                    .ifPresent(queue -> playAudio());
            }

            @Override
            public void onResolutionChanged(int width, int height) {
            }

            @Override
            public void onPlayBackComplete() {
                LogUtil.debug(TAG, "Audio on play back complete");
                Optional.ofNullable(audioQueue)
                    .filter(queue -> !queue.isEmpty())
                    .ifPresent(queue -> playAudio());
            }

            @Override
            public void onRewindToComplete() {
            }

            @Override
            public void onBufferingChange(int percent) {
            }

            @Override
            public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {
            }

            @Override
            public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
            }
        };
    }
}
